diff --git a/web/avatars/avatar.css b/web/avatars/avatar.css
index 86ba383a1..27ca9ccbe 100644
--- a/web/avatars/avatar.css
+++ b/web/avatars/avatar.css
@@ -1,61 +1,72 @@
+.avatarContainer {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.editAvatarLoadingSpinner {
+ position: absolute;
+}
+
.emojiContainer {
display: flex;
align-items: center;
justify-content: center;
}
.emojiMicro {
font-size: 9px;
height: 16px;
line-height: 16px;
}
.emojiSmall {
font-size: 14px;
height: 24px;
line-height: 24px;
}
.emojiLarge {
font-size: 28px;
height: 42px;
line-height: 42px;
}
.emojiProfile {
font-size: 80px;
height: 112px;
line-height: 112px;
}
.micro {
border-radius: 8px;
height: 16px;
width: 16px;
min-width: 16px;
}
.small {
border-radius: 12px;
height: 24px;
width: 24px;
min-width: 24px;
}
.large {
border-radius: 21px;
height: 42px;
width: 42px;
min-width: 42px;
}
.profile {
border-radius: 56px;
height: 112px;
width: 112px;
min-width: 112px;
}
.imgContainer {
object-fit: cover;
}
diff --git a/web/avatars/avatar.react.js b/web/avatars/avatar.react.js
index 108f9ade1..507be4ed5 100644
--- a/web/avatars/avatar.react.js
+++ b/web/avatars/avatar.react.js
@@ -1,69 +1,96 @@
// @flow
import classnames from 'classnames';
import * as React from 'react';
import type { ResolvedClientAvatar } from 'lib/types/avatar-types.js';
import css from './avatar.css';
+import LoadingIndicator from '../loading-indicator.react.js';
type Props = {
+avatarInfo: ResolvedClientAvatar,
+size: 'micro' | 'small' | 'large' | 'profile',
+ +showSpinner?: boolean,
};
function Avatar(props: Props): React.Node {
- const { avatarInfo, size } = props;
+ const { avatarInfo, size, showSpinner } = props;
const containerSizeClassName = classnames({
[css.imgContainer]: avatarInfo.type === 'image',
[css.micro]: size === 'micro',
[css.small]: size === 'small',
[css.large]: size === 'large',
[css.profile]: size === 'profile',
});
const emojiSizeClassName = classnames({
[css.emojiContainer]: true,
[css.emojiMicro]: size === 'micro',
[css.emojiSmall]: size === 'small',
[css.emojiLarge]: size === 'large',
[css.emojiProfile]: size === 'profile',
});
const emojiContainerColorStyle = React.useMemo(() => {
if (avatarInfo.type === 'emoji') {
return { backgroundColor: `#${avatarInfo.color}` };
}
return undefined;
}, [avatarInfo.color, avatarInfo.type]);
const avatar = React.useMemo(() => {
if (avatarInfo.type === 'image') {
return (
);
}
return (
);
}, [
avatarInfo.emoji,
avatarInfo.type,
avatarInfo.uri,
containerSizeClassName,
emojiContainerColorStyle,
emojiSizeClassName,
]);
- return avatar;
+ let loadingIndicatorSize;
+ if (size === 'micro') {
+ loadingIndicatorSize = 'small';
+ } else if (size === 'small') {
+ loadingIndicatorSize = 'small';
+ } else if (size === 'large') {
+ loadingIndicatorSize = 'medium';
+ } else {
+ loadingIndicatorSize = 'large';
+ }
+
+ const loadingIndicator = React.useMemo(
+ () => (
+
+
+
+ ),
+ [loadingIndicatorSize],
+ );
+
+ return (
+
+ {showSpinner ? loadingIndicator : null}
+ {avatar}
+
+ );
}
export default Avatar;
diff --git a/web/avatars/emoji-avatar-selection-modal.react.js b/web/avatars/emoji-avatar-selection-modal.react.js
index 68d4292a8..567294c76 100644
--- a/web/avatars/emoji-avatar-selection-modal.react.js
+++ b/web/avatars/emoji-avatar-selection-modal.react.js
@@ -1,145 +1,149 @@
// @flow
import data from '@emoji-mart/data';
import Picker from '@emoji-mart/react';
import invariant from 'invariant';
import * as React from 'react';
import { EditUserAvatarContext } from 'lib/components/base-edit-user-avatar-provider.react.js';
import { useModalContext } from 'lib/components/modal-provider.react.js';
import SWMansionIcon from 'lib/components/SWMansionIcon.react.js';
import {
defaultAnonymousUserEmojiAvatar,
getAvatarForUser,
getDefaultAvatar,
} from 'lib/shared/avatar-utils.js';
import type {
ClientAvatar,
ClientEmojiAvatar,
} from 'lib/types/avatar-types.js';
import Avatar from './avatar.react.js';
import css from './emoji-avatar-selection-modal.css';
import Button, { buttonThemes } from '../components/button.react.js';
import LoadingIndicator from '../loading-indicator.react.js';
import Modal from '../modals/modal.react.js';
import ColorSelector from '../modals/threads/color-selector.react.js';
import { useSelector } from '../redux/redux-utils.js';
function EmojiAvatarSelectionModal(): React.Node {
const { popModal } = useModalContext();
const editUserAvatarContext = React.useContext(EditUserAvatarContext);
invariant(editUserAvatarContext, 'editUserAvatarContext should be set');
const { setUserAvatar, userAvatarSaveInProgress } = editUserAvatarContext;
const [updateAvatarStatus, setUpdateAvatarStatus] =
React.useState('success' | 'failure')>();
const currentUserInfo = useSelector(state => state.currentUserInfo);
const currentUserAvatar: ClientAvatar = getAvatarForUser(currentUserInfo);
const defaultUserAvatar: ClientEmojiAvatar = currentUserInfo?.username
? getDefaultAvatar(currentUserInfo.username)
: defaultAnonymousUserEmojiAvatar;
// eslint-disable-next-line no-unused-vars
const [pendingAvatarEmoji, setPendingAvatarEmoji] = React.useState(
currentUserAvatar.type === 'emoji'
? currentUserAvatar.emoji
: defaultUserAvatar.emoji,
);
const [pendingAvatarColor, setPendingAvatarColor] = React.useState(
currentUserAvatar.type === 'emoji'
? currentUserAvatar.color
: defaultUserAvatar.color,
);
const pendingEmojiAvatar: ClientEmojiAvatar = React.useMemo(
() => ({
type: 'emoji',
emoji: pendingAvatarEmoji,
color: pendingAvatarColor,
}),
[pendingAvatarColor, pendingAvatarEmoji],
);
const onEmojiSelect = React.useCallback(selection => {
setUpdateAvatarStatus();
setPendingAvatarEmoji(selection.native);
}, []);
const onColorSelection = React.useCallback((hex: string) => {
setUpdateAvatarStatus();
setPendingAvatarColor(hex);
}, []);
const onSaveAvatar = React.useCallback(async () => {
try {
await setUserAvatar(pendingEmojiAvatar);
setUpdateAvatarStatus('success');
} catch {
setUpdateAvatarStatus('failure');
}
}, [pendingEmojiAvatar, setUserAvatar]);
let saveButtonContent;
let buttonColor;
if (userAvatarSaveInProgress) {
buttonColor = buttonThemes.standard;
saveButtonContent = ;
} else if (updateAvatarStatus === 'success') {
buttonColor = buttonThemes.success;
saveButtonContent = (
<>
{'Avatar update succeeded.'}
>
);
} else if (updateAvatarStatus === 'failure') {
buttonColor = buttonThemes.danger;
saveButtonContent = (
<>
{'Avatar update failed. Please try again.'}
>
);
} else {
buttonColor = buttonThemes.standard;
saveButtonContent = 'Save Avatar';
}
return (
);
}
export default EmojiAvatarSelectionModal;